//
// Created by 33145 on 2023/1/29.
//
#include <functional>
#include <dlfcn.h>
#include <stdarg.h>
#include <time.h>

#include "hook.h"
#include "fiber.h"
#include "iomanager.h"
#include "log.h"
#include "config.h"
#include "fd_manager.h"

sylar::Logger::ptr g_logger = SYLAR_LOG_NAME("system");
namespace sylar {
  static sylar::ConfigVar<int>::ptr g_tcp_connect_timeout =
          sylar::Config::LookUp("tcp.connect.timeout", 5000, "tcp connect timeout");
  // 为每个线程设置是否hook
  static thread_local bool t_hook_enable = false;

#define HOOK_FUN(XX) \
  XX(sleep)          \
  XX(usleep)         \
  XX(nanosleep)      \
  XX(socket)         \
  XX(connect)        \
  XX(accept)         \
  XX(read)           \
  XX(readv)          \
  XX(recv)           \
  XX(recvfrom)       \
  XX(recvmsg)        \
  XX(write)          \
  XX(writev)         \
  XX(send)           \
  XX(sendto)         \
  XX(sendmsg)        \
  XX(close)          \
  XX(fcntl)          \
  XX(ioctl)          \
  XX(getsockopt)     \
  XX(setsockopt)

  void hook_init() {
    static bool is_inited = false;
    if (is_inited) {
      return;
    }
/**
 * 从系统库里取出被 “全局符号介入”屏蔽掉的函数  ##表示连接符,连接前后的字符
 * sleep_f = (sleep_fun) dlsym(((void *) -1l), "sleep");
 * usleep_f = (usleep_fun) dlsym(((void *) -1l), "usleep");
 * ...
 * **/
#define XX(name) name ## _f = (name ## _fun)dlsym(RTLD_NEXT, #name);
    HOOK_FUN(XX);
#undef XX
  }
  static uint64_t s_connect_timeout = -1;
  struct _HookIniter {
    _HookIniter() {
      hook_init();
      s_connect_timeout = g_tcp_connect_timeout->getValue();

      g_tcp_connect_timeout->addListener([](const int& old_value, const int& new_value){
        SYLAR_LOG_INFO(g_logger) << "tcp connect timeout changed from "
                                 << old_value << " to " << new_value;
        s_connect_timeout = new_value;
      });
    }
  };

  static _HookIniter s_hook_initer;

  bool is_hook_enable() {
    return t_hook_enable;
  }
  void set_hook_enable(bool flag) {
    t_hook_enable = flag;
  }
}

struct timer_info {
  int cancelled = 0;
};

template<typename OriginFun, typename ... Args>
static ssize_t do_io(int fd, OriginFun fun, const char* hook_fun_name, uint32_t event,
                     int timeout_so, Args&&... args) {
  if (!sylar::t_hook_enable) {
    return fun(fd, std::forward<Args>(args)...);
  }
  sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(fd);
  if (!ctx) {
    return fun(fd, std::forward<Args>(args)...);
  }
  if (ctx->isClose()) {
    errno = EBADF;
    return -1;
  }
  /// 如果不是socket或已经被设置为非阻塞
  if (!ctx->isSocket() || ctx->getUserNonblock()) {
    return fun(fd, std::forward<Args>(args)...);
  }
  uint64_t to = ctx->getTimeout(timeout_so);
  std::shared_ptr<timer_info> tinfo(new timer_info);

retry:

  ssize_t n = fun(fd, std::forward<Args>(args)...);
  /// EINTR Interrupted system call (系统调用中断)
  while(n == -1 && errno == EINTR) {
    n = fun(fd, std::forward<Args>(args)...);
  }

  if (n == -1 && errno == EAGAIN) {
    sylar::IOManager* iom = sylar::IOManager::GetThis();  // 获得当前线程的IOManager
    sylar::Timer::ptr timer;
    std::weak_ptr<timer_info> winfo(tinfo);
    /// 如果socket有设置超时时间（写超时或读超时）
    if (to != (uint64_t)-1) {
      /// 条件定时器：通过将weak_ptr作为条件，绑定一个shared_ptr，如果shared_ptr先失效则表示条件失败
      /// 在hook里即体现为任务在超时前触发，函数退出，shared_ptr失效，条件为false
      timer = iom->addConditionTimer(to, [winfo, fd, iom, event](){
          auto t = winfo.lock();
          if (!t || t->cancelled) {
            return;
          }
          t->cancelled = ETIMEDOUT;
          iom->cancelEvent(fd, (sylar::IOManager::Event)(event));
        }, winfo);
    }
    /// 未显示指定回调函数表示默认以当前协橙作为回调，然后直接yield，下次会直接从当前位置执行
    /// 所以hook要用协橙实现
    int rt = iom->addEvent(fd, (sylar::IOManager::Event)(event));
    // 添加失败
    if (rt != 0) {
        SYLAR_LOG_ERROR(g_logger) << hook_fun_name << " addEvent("
                                  << fd << ", " << event << ")";
      if (timer) {
        timer->cancel();
      }
      return -1;
    } else {
      /// 有两个唤醒条件： 被上面添加的定时器唤醒  被事件唤醒
      sylar::Fiber::YieldToHold();
      if (timer) {
        timer->cancel();
      }
      /// 说明超时, 即在任务被超时唤醒之后cancelled还没被重新设置
      if (tinfo->cancelled) {
        errno = tinfo->cancelled;
        return -1;
      }
      goto retry;
    }
  }
  return n;
}

extern "C" {
#define XX(name) name ## _fun name ## _f = nullptr;
  HOOK_FUN(XX);
#undef XX

  unsigned int sleep(unsigned int seconds) {
    // std::cout << "hook sleep" << std::endl;
    if (!sylar::t_hook_enable) {
      return sleep_f(seconds);
    }
    sylar::Fiber::ptr fiber = sylar::Fiber::GetThis();    // 获得当前线程的协橙
    sylar::IOManager* iom = sylar::IOManager::GetThis();  // 获得当前线程的调度器
    // iom->addTimer(seconds * 1000, std::bind(&sylar::IOManager::schedule<sylar::Fiber::ptr>, iom, fiber));
    // std::cout << "hello" << std::endl;
    iom->addTimer(seconds * 1000, [iom, fiber](){
      iom->schedule(fiber);
    });
    // std::cout << "world" << std::endl;
    sylar::Fiber::YieldToHold();
    return 0;
  }
  int usleep(useconds_t usec) {
    if (!sylar::t_hook_enable) {
      return usleep_f(usec);
    }
    sylar::Fiber::ptr fiber = sylar::Fiber::GetThis();
    sylar::IOManager* iom = sylar::IOManager::GetThis();
    iom->addTimer(usec / 1000, [iom, fiber](){
      iom->schedule(fiber);
    });
    sylar::Fiber::YieldToHold();
    return 0;
  }

  int nanosleep(const struct timespec *req, struct timespec *rem) {
    if (!sylar::t_hook_enable) {
      return nanosleep_f(req, rem);
    }
    int timeout_ms = req->tv_sec * 1000 + req->tv_nsec / 1000 / 1000;
    sylar::Fiber::ptr fiber = sylar::Fiber::GetThis();
    sylar::IOManager* iom = sylar::IOManager::GetThis();
    iom->addTimer(timeout_ms / 1000, [iom, fiber](){
      iom->schedule(fiber);
    });
    sylar::Fiber::YieldToHold();
    return 0;
  }

  int socket(int domain, int type, int protocol) {
    if (!sylar::t_hook_enable) {
      return socket_f(domain, type, protocol);
    }
    int fd = socket_f(domain, type, protocol);
    if (fd == -1) {
      return fd;
    }
    /// 把socket的fd加入到管理对象中
    sylar::FdMgr::GetInstance()->get(fd, true);
    return fd;
  }
  /**
   * @details:  1.判断传入的fd是否为套接字，如果不为套接字，则调用系统的connect函数并返回。
   *            2.判断fd是否被显式设置为了非阻塞模式，如果是则调用系统的connect函数并返回。
   *            3.调用系统的connect函数，由于套接字是非阻塞的，这里会直接返回EINPROGRESS错误。
   *            4.如果超时参数有效，则添加一个条件定时器，在定时时间到后通过t->cancelled设置超时标志并触发一次WRITE事件。
   *            5.添加WRITE事件并yield，等待WRITE事件触发再往下执行。
   *            6.等待超时或套接字可写，如果先超时，则条件变量winfo仍然有效，通过winfo来设置超时标志并触发WRITE事件，
   *              协程从yield点返回，返回之后通过超时标志设置errno并返回-1；如果在未超时之前套接字就可写了，那么直接取消定时器并返回成功。
   *              取消定时器会导致定时器回调被强制执行一次，但这并不会导致问题，因为只有当前协程结束后，定时器回调才会在接下来被调度，
   *              由于定时器回调被执行时connect_with_timeout协程已经执行完了，所以理所当然地条件变量也被释放了，所以实际上定时器回调函数什么也没做
   * */
  int connect_with_timeout(int fd, const struct sockaddr* addr, socklen_t addrlen, uint64_t timeout_ms) {
    if(!sylar::t_hook_enable) {
      return connect_f(fd, addr, addrlen);
    }
    sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(fd);
    if(!ctx || ctx->isClose()) {
      errno = EBADF;
      return -1;
    }

    if(!ctx->isSocket()) {
      return connect_f(fd, addr, addrlen);
    }

    if(ctx->getUserNonblock()) {
      return connect_f(fd, addr, addrlen);
    }

    int n = connect_f(fd, addr, addrlen);
    if(n == 0) {
      return 0;
    } else if(n != -1 || errno != EINPROGRESS) {
      return n;
    }

    sylar::IOManager* iom = sylar::IOManager::GetThis();
    sylar::Timer::ptr timer;
    std::shared_ptr<timer_info> tinfo(new timer_info);
    std::weak_ptr<timer_info> winfo(tinfo);

    if(timeout_ms != (uint64_t)-1) {
      timer = iom->addConditionTimer(timeout_ms, [winfo, fd, iom]() {
        auto t = winfo.lock();
        if(!t || t->cancelled) {
          return;
        }
        t->cancelled = ETIMEDOUT;
        iom->cancelEvent(fd, sylar::IOManager::WRITE);
      }, winfo);
    }

    int rt = iom->addEvent(fd, sylar::IOManager::WRITE);
    if(rt == 0) {
      sylar::Fiber::YieldToHold();
      if(timer) {
        timer->cancel();
      }
      if(tinfo->cancelled) {
        errno = tinfo->cancelled;
        return -1;
      }
    } else {
      if(timer) {
        timer->cancel();
      }
      SYLAR_LOG_ERROR(g_logger) << "connect addEvent(" << fd << ", WRITE) error";
    }

    int error = 0;
    socklen_t len = sizeof(int);
    if(-1 == getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len)) {
      return -1;
    }
    if(!error) {
      return 0;
    } else {
      errno = error;
      return -1;
    }
  }

  int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
    return connect_with_timeout(sockfd, addr, addrlen, sylar::s_connect_timeout);
  }

  int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
    int fd = (int)do_io(sockfd, accept_f, "accept", sylar::IOManager::READ, SO_RCVTIMEO, addr, addrlen);
    if (fd >= 0) {
      sylar::FdMgr::GetInstance()->get(fd, true);
    }
    return fd;
  }

  ssize_t read(int fd, void *buf, size_t count) {
    return do_io(fd, read_f, "read", sylar::IOManager::READ, SO_RCVTIMEO, buf, count);
  }

  ssize_t readv(int fd, const struct iovec *iov, int iovcnt) {
    return do_io(fd, readv_f, "readv", sylar::IOManager::READ, SO_RCVTIMEO, iov, iovcnt);
  }

  ssize_t recv(int sockfd, void *buf, size_t len, int flags) {
    return do_io(sockfd, recv_f, "recv", sylar::IOManager::READ, SO_RCVTIMEO, buf, len, flags);
  }

  ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                                struct sockaddr *src_addr, socklen_t *addrlen) {
    return do_io(sockfd, recvfrom_f, "recvfrom", sylar::IOManager::READ, SO_RCVTIMEO, buf,
                 len, flags, src_addr, addrlen);
  }

  ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags) {
    return do_io(sockfd, recvmsg_f, "recvmsg", sylar::IOManager::READ, SO_RCVTIMEO, msg, flags);
  }

  ssize_t write(int fd, const void *buf, size_t count) {
    return do_io(fd, write_f, "write", sylar::IOManager::WRITE, SO_SNDTIMEO, buf, count);
  }

  ssize_t writev(int fd, const struct iovec *iov, int iovcnt) {
    return do_io(fd, writev_f, "writev", sylar::IOManager::WRITE, SO_SNDTIMEO, iov, iovcnt);
  }

  ssize_t send(int s, const void *msg, size_t len, int flags) {
    return do_io(s, send_f, "send", sylar::IOManager::WRITE, SO_SNDTIMEO, msg, len, flags);
  }

  ssize_t sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) {
    return do_io(s, sendto_f, "sendto", sylar::IOManager::WRITE, SO_SNDTIMEO, msg, len, flags, to, tolen);
  }

  ssize_t sendmsg(int s, const struct msghdr *msg, int flags) {
    return do_io(s, sendmsg_f, "sendmsg", sylar::IOManager::WRITE, SO_SNDTIMEO, msg, flags);
  }

  int close(int fd) {
    if(!sylar::t_hook_enable) {
      return close_f(fd);
    }

    sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(fd);
    if(ctx) {
      auto iom = sylar::IOManager::GetThis();
      if(iom) {
        iom->cancelAll(fd);
      }
      sylar::FdMgr::GetInstance()->del(fd);
    }
    return close_f(fd);
  }

int fcntl(int fd, int cmd, ... /* arg */ ) {
  va_list va;
  va_start(va, cmd);
  switch(cmd) {
    case F_SETFL:
    {
      int arg = va_arg(va, int);
      va_end(va);
      sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(fd);
      if(!ctx || ctx->isClose() || !ctx->isSocket()) {
        return fcntl_f(fd, cmd, arg);
      }
      ctx->setUserNonblock(arg & O_NONBLOCK);
      if(ctx->getSysNonblock()) {
        arg |= O_NONBLOCK;
      } else {
        arg &= ~O_NONBLOCK;
      }
      return fcntl_f(fd, cmd, arg);
    }
      break;
    case F_GETFL:
    {
      va_end(va);
      int arg = fcntl_f(fd, cmd);
      sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(fd);
      if(!ctx || ctx->isClose() || !ctx->isSocket()) {
        return arg;
      }
      if(ctx->getUserNonblock()) {
        return arg | O_NONBLOCK;
      } else {
        return arg & ~O_NONBLOCK;
      }
    }
      break;
    case F_DUPFD:
    case F_DUPFD_CLOEXEC:
    case F_SETFD:
    case F_SETOWN:
    case F_SETSIG:
    case F_SETLEASE:
    case F_NOTIFY:
#ifdef F_SETPIPE_SZ
    case F_SETPIPE_SZ:
#endif
    {
      int arg = va_arg(va, int);
      va_end(va);
      return fcntl_f(fd, cmd, arg);
    }
      break;
    case F_GETFD:
    case F_GETOWN:
    case F_GETSIG:
    case F_GETLEASE:
#ifdef F_GETPIPE_SZ
    case F_GETPIPE_SZ:
#endif
    {
      va_end(va);
      return fcntl_f(fd, cmd);
    }
      break;
    case F_SETLK:
    case F_SETLKW:
    case F_GETLK:
    {
      struct flock* arg = va_arg(va, struct flock*);
      va_end(va);
      return fcntl_f(fd, cmd, arg);
    }
      break;
    case F_GETOWN_EX:
    case F_SETOWN_EX:
    {
      struct f_owner_exlock* arg = va_arg(va, struct f_owner_exlock*);
      va_end(va);
      return fcntl_f(fd, cmd, arg);
    }
      break;
    default:
      va_end(va);
      return fcntl_f(fd, cmd);
  }
}

int ioctl(int d, unsigned long int request, ...) {
  va_list va;
  va_start(va, request);
  void* arg = va_arg(va, void*);
  va_end(va);

  if(FIONBIO == request) {
    bool user_nonblock = !!*(int*)arg;
    sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(d);
    if(!ctx || ctx->isClose() || !ctx->isSocket()) {
      return ioctl_f(d, request, arg);
    }
    ctx->setUserNonblock(user_nonblock);
  }
  return ioctl_f(d, request, arg);
}

  int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) {
    return getsockopt_f(sockfd, level, optname, optval, optlen);
  }

  int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) {
    if(!sylar::t_hook_enable) {
      return setsockopt_f(sockfd, level, optname, optval, optlen);
    }
    if(level == SOL_SOCKET) {
      if(optname == SO_RCVTIMEO || optname == SO_SNDTIMEO) {
        sylar::FdCtx::ptr ctx = sylar::FdMgr::GetInstance()->get(sockfd);
        if(ctx) {
          const timeval* v = (const timeval*)optval;
          ctx->setTimeout(optname, v->tv_sec * 1000 + v->tv_usec / 1000);
        }
      }
    }
    return setsockopt_f(sockfd, level, optname, optval, optlen);
  }

}
